package com.hainingtong.core.controller;

import com.hainingtong.core.config.JwtProperties;
import com.hainingtong.core.constant.Constants;
import com.hainingtong.core.dto.LoginUserDto;
import com.hainingtong.core.dto.RegisterUserDto;
import com.hainingtong.core.dto.group.DefaultGroup;
import com.hainingtong.core.dto.group.PhoneCodeGroup;
import com.hainingtong.core.dto.group.UsernamePasswordGroup;
import com.hainingtong.core.exception.NicknameExistsException;
import com.hainingtong.core.exception.PrincipalExistsException;
import com.hainingtong.core.exception.TokenInvalidException;
import com.hainingtong.core.service.UserService;
import com.hainingtong.core.shiro.token.LoginType;
import com.hainingtong.core.shiro.token.TokenUtil;
import com.hainingtong.core.util.JwtUtil;
import com.hainingtong.core.util.RedisUtil;
import com.hainingtong.core.util.Result;
import com.hainingtong.core.util.ResultCode;
import lombok.extern.slf4j.Slf4j;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.IncorrectCredentialsException;
import org.apache.shiro.authc.UnknownAccountException;
import org.apache.shiro.authz.AuthorizationException;
import org.apache.shiro.authz.annotation.RequiresRoles;
import org.apache.shiro.subject.Subject;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;

import java.util.HashMap;
import java.util.Map;
import java.util.Random;
import java.util.UUID;


@RestController
@Slf4j
@RequestMapping("/api/v1/user")
public class UserController {

    @Autowired
    JwtProperties jwtProperties;

    @Autowired
    UserService userService;

    @RequiresRoles({"admin"})
    @GetMapping("/test")
    Result test() {
        return Result.ok();
    }


    @PostMapping("/register/get/phoneCode/{phone:1[3-9]\\d{9}}")
    Result getPhoneCode(
            @PathVariable String phone
    ) {
        // 生成一个4位随机数，作为验证码
        String str = "0123456789";
        StringBuilder sb = new StringBuilder(4);
        for (int i = 0; i < 4; i++) {
            char ch = str.charAt(new Random().nextInt(str.length()));
            sb.append(ch);
        }
        String code = sb.toString();


        // 用uuid作为验证码编号
        String id = UUID.randomUUID().toString().replaceAll("-", "");


        // 调用接口发送验证码
        log.info("手机验证码：{}", code); // 此处简单的输出

        // 保存验证码到redis
        RedisUtil.save(Constants.REDIS_PHONE_CODE_PREFIX + phone + id, code);

        // 返回验证码和编号到前端，编号在注册的时候要带上，用于从redis中查询手机验证码来比较
        return Result.ok(id);
    }

    @PostMapping("/register/PhoneCode/{id:[\\da-z]{32}}")
    Result registerPhoneCode(
            @PathVariable String id,
            @Validated({PhoneCodeGroup.class, DefaultGroup.class})
            @RequestBody RegisterUserDto registerUserDto) throws Exception {

        // 从redis获取验证码
        String code = (String) RedisUtil.get(Constants.REDIS_PHONE_CODE_PREFIX + registerUserDto.getPrincipal() + id);

        // 比较验证码是否正确
        if (!registerUserDto.getCredentials().equals(code)) {
            return Result.error(ResultCode.ERROR_PHONE_CODE_INVALID);
        }

        return register(registerUserDto);
    }

    /**
     * 约定/register/后面跟验证类型，方便前端拼接api请求地址
     *
     * @param registerUserDto
     * @return
     * @throws Exception
     */
    @PostMapping("/register/UsernamePassword")
    Result registerUsernamePassword(
            @Validated({UsernamePasswordGroup.class, DefaultGroup.class})
            @RequestBody RegisterUserDto registerUserDto) throws Exception {
        return register(registerUserDto);
    }

    Result register(@RequestBody RegisterUserDto registerUserDto) throws Exception {

        try {
            Object user = userService.register(
                    registerUserDto.getNickname(),
                    registerUserDto.getPrincipal(),
                    registerUserDto.getCredentials(),
                    registerUserDto.getType());

            return Result.ok(ResultCode.SUCCESS_REGISTER, user);
        } catch (NicknameExistsException e) {
            return Result.error(ResultCode.ERROR_NICKNAME_EXISTS);
        } catch (PrincipalExistsException e) {
            return Result.error(ResultCode.ERROR_PRINCIPAL_EXISTS);
        }
    }

    @PostMapping("/login")
    Result login(@RequestBody LoginUserDto user) {

        Subject subject = SecurityUtils.getSubject();

        // 根据用户数据创建不同类型的 token
        AuthenticationToken token = TokenUtil.createToken(
                user.getUsername(),
                user.getPassword(),
                LoginType.valueOf(user.getType())
        );

        try {
            //进行验证，这里可以捕获异常，然后返回对应信息
            subject.login(token);
        } catch (UnknownAccountException e) {
            log.warn("账号不存在！", e);
            return Result.error(ResultCode.ERROR_PRINCIPAL_NOT_EXISTS);
        } catch (IncorrectCredentialsException e) {
            log.warn("账号或密码错误！", e);
            return Result.error(ResultCode.ERROR_PASSWORD);
        } catch (AuthorizationException e) {
            log.warn("没有权限！", e);
            return Result.error(ResultCode.ERROR_NO_PERMISSION);
        } catch (AuthenticationException e) {
            log.error("其他错误：", e);
            return Result.error(ResultCode.UNKNOWN_REASON, e.getMessage());
        }

        Map<String,String> d = new HashMap<>();
        // 带上name和type的原因是在jwtRealm中需要用name和type确定用户
        d.put("principal", user.getUsername());
        d.put("type", user.getType());

        // 生成jwt token返回给客户端
        String tokenStr = JwtUtil.sign(d, jwtProperties.getSecretKey());
        HashMap<String, Object> data = new HashMap<>();
        data.put("token", tokenStr);
        data.put("user", subject.getPrincipal());
        return Result.ok(ResultCode.SUCCESS_LOGIN, data);
    }
}
